class Compile {
  constructor(el, vm) {
    this.$el = document.querySelector(el);
    this.$vm = vm;

    if (this.$el) {
      // 提取宿主中模板内容到Fragment标签，dom操作会提⾼效率
      this.$fragment = this.node2Fragment(this.$el);
      // 编译模板内容，同时进⾏依赖收集
      this.compile(this.$fragment);
      // 把编译好的内容插入到根节点元素中
      this.$el.appendChild(this.$fragment);
    }
  }

  node2Fragment(el) {
    const fragment = document.createDocumentFragment();
    let child;
    while ((child = el.firstChild)) {
      fragment.appendChild(child);
    }
    return fragment;
  }

  // 1. 编译方法
  compile(el) {
    const childNodes = el.childNodes;
    // 1.1 遍历子节点，根据节点类型，执行相应的编译方法
    Array.from(childNodes).forEach((node) => {
      // 1.1.1 如果是元素节点，遍历该元素节点上的属性，看有没有指令属性或事件属性
      if (this.isElement(node)) {
        const nodeAttrs = node.attributes;
        Array.from(nodeAttrs).forEach((attr) => {
          const attrName = attr.name; //属性名
          const attrVal = attr.value; //属性值
          if (this.isDirective(attrName)) {
            // 1.1.2 如果有指令属性：x-text, x-html, x-model...
            const dir =
              attrName.substring(2, 3).toUpperCase() + attrName.substring(3); // dir: 'Text', 'Html', 'Model'
            this["compile" + dir] &&
              this["compile" + dir](node, this.$vm, attrVal); // attrVal: 'name', 'age', 'template'
          } else if (this.isEvent(attrName)) {
            const dir = attrName.substring(1); // dir: 'click'
            // 1.1.3 如果有事件属性：@click...
            this.compilEevent(node, this.$vm, attrVal, dir); // attrVal: 'changeName', 'changeAge'
          }
        });
        // 1.1.4 如果是插值文本: {{XXX}}
      } else if (this.isInterpolation(node)) {
        this.compileInterpolation(node);
      }
      // 1.2 递归子节点
      if (node.childNodes && node.childNodes.length > 0) {
        this.compile(node);
      }
    });
  }

  // 2. 判断节点类型
  // 2.1 元素节点
  isElement(node) {
    return node.nodeType === 1;
  }
  // 2.2 指令属性
  isDirective(attr) {
    return attr.indexOf("x-") === 0;
  }
  // 2.3 事件属性
  isEvent(attr) {
    return attr.indexOf("@") === 0;
  }
  // 2.4 插值文本{{xxx}}节点
  isInterpolation(node) {
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
  }

  // 3. 不同类型节点的编译方法
  // 3.1 编译指令
  // 3.1.1 编译 x-text 指令
  compileText(node, vm, dataKey) {
    this.update(node, vm, dataKey, "text");
  }
  // 3.1.2 编译 x-html 指令
  compileHtml(node, vm, dataKey) {
    this.update(node, vm, dataKey, "html");
  }
  // 3.1.3 编译 x-model 指令
  compileModel(node, vm, dataKey) {
    // 指定input的value属性
    this.update(node, vm, dataKey, "model");
    // 视图对模型响应:双向绑定
    node.addEventListener("input", (e) => {
      vm[dataKey] = e.target.value;
    });
  }
  // 3.2 编译事件@
  compilEevent(node, vm, dataKey, dir) {
    let fn = vm.$options.methods && vm.$options.methods[dataKey];
    if (dir && fn) {
      node.addEventListener(dir, fn.bind(vm));
    }
  }
  // 3.3 编译插值文本：{{xxx}}
  compileInterpolation(node) {
    const dataKey = RegExp.$1; // dataKey: name
    this.update(node, this.$vm, dataKey, "text");
  }
  // 4. 公共的更新方法：初次渲染视图和后续响应式更新视图
  update(node, vm, dataKey, dir) {
    const updateFn = this[dir + "Update"];
    // 4.1 初始化
    updateFn && updateFn(node, vm[dataKey]);
    // 4.2 依赖收集，当data变化，更新视图
    new Watcher(vm, dataKey, function (newVal) {
      updateFn && updateFn(node, newVal);
    });
  }

  // 5. 不同类型节点的渲染方法
  // 5.1 文本节点
  textUpdate(node, val) {
    node.textContent = val;
  }
  // 5.2 html节点x-html
  htmlUpdate(node, val) {
    node.innerHTML = val;
  }
  // 5.3 双向数据绑定
  modelUpdate(node, val) {
    node.value = val;
  }
}
